﻿#region 方法
// 方法
// 访问修饰符 返回值 方法名 (参数)
// {
//      方法主体
// }

// 访问修饰符 public 公用的 protected 保护的 private 私有的
// 可省略 如果省略 就是用默认值 private

// 返回值 方法返回的数据类型
// 非空类型必须有return; 且返回的类型需要匹配

// 方法名 大小写敏感 不能使用关键字 数字开头 特殊符号
// 只能以字母或下划线开头 （约定俗成的）首字母大写

// 参数列表 用()括起来 多个用,隔开
// 类型 参数名
// 参数的默认值需要从右往左给 因为默认值可以省略

// 方法的调用 方法名(参数列表);
// 参数列表数量需要一一对应，有默认值的可以省略


//int _Test(int a, int b, int c=0)
//{
//    return 1;
//}

//_Test(1, 1);
#endregion


// 游戏循环框架
// 游戏本质上就是一个死循环
// 初始化阶段 Init
// 更新阶段 Update
// 渲染（显示）阶段 Render
// 结束阶段 End
// 原则 专门的事放在专门的方法内


using static System.Runtime.InteropServices.JavaScript.JSType;

bool isOver = false;
int fps = 60;
// 获取时间
// 1 s = 1000 ms = 1000 000 us = 1000 000 000 ns = 1000 000 000 000 ps
// 1 Tick = 100 ns
// 1 s = 10 000 000 Tick
long currentTime, lastTime;
ConsoleKeyInfo key;
Pos pos;
Dir dir;
string fileName = "../../../map.csv";
int[,] map;

// 输入
void Input()
{
    // 检测按键
    // 只更改状态 不做逻辑处理
    key = Console.ReadKey(true);  // 参数控制是否显示字符
    switch (key.Key)
    {
        case ConsoleKey.UpArrow:
        case ConsoleKey.W:
            dir = Dir.Up;
            break;
        case ConsoleKey.DownArrow:
        case ConsoleKey.S:
            dir = Dir.Down;
            break;
        case ConsoleKey.LeftArrow:
        case ConsoleKey.A:
            dir = Dir.Left;
            break;
        case ConsoleKey.RightArrow:
        case ConsoleKey.D:
            dir = Dir.Right;
            break;
        default:
            break;
    }
}

// 绘制地图
void DrawMap(int[,] data)
{
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            // 根据地图数据 绘制相应符号
            //if (data[i, j] == (int)MapType.Wall)
            //{
            //    // 如果地图数据是“墙” 那就画墙
            //    Console.SetCursorPosition(j, i); Console.Write("0");
            //}
            Console.SetCursorPosition(j, i);
            switch (data[i, j])
            {
                case (int)MapType.None:
                    Console.WriteLine(" ");
                    break;
                case (int)MapType.Wall:
                    Console.Write("0");
                    break;
                case (int)MapType.Box:
                    Console.Write("+");
                    break;
                case (int)MapType.Exit:
                    Console.Write("E");
                    break;
                default:
                    break;
            }
        }
    }
}


void Move()
{
    // 根据移动方向修改坐标
    switch (dir)
    {
        case Dir.Up:
            pos.y--;
            break;
        case Dir.Down:
            pos.y++;
            break;
        case Dir.Left:
            pos.x--;
            break;
        case Dir.Right:
            pos.x++;
            break;
    }
}

// 获取下一格坐标
Pos GetNextPos()
{
    Pos next;
    next.x = next.y = 0;
    next.x0 = next.y0 = 0;
    switch (dir)
    {
        case Dir.Up:
            next.x = pos.x;
            next.y = pos.y - 1;
            break;
        case Dir.Down:
            next.x = pos.x;
            next.y = pos.y + 1;
            break;
        case Dir.Left:
            next.x = pos.x - 1;
            next.y = pos.y;
            break;
        case Dir.Right:
            next.x = pos.x + 1;
            next.y = pos.y;
            break;
    }
    return next;
}

// 获取下下一格坐标
Pos GetNextNextPos()
{
    Pos next;
    next.x = next.y = 0;
    next.x0 = next.y0 = 0;
    switch (dir)
    {
        case Dir.Up:
            next.x = pos.x;
            next.y = pos.y - 2;
            break;
        case Dir.Down:
            next.x = pos.x;
            next.y = pos.y + 2;
            break;
        case Dir.Left:
            next.x = pos.x - 2;
            next.y = pos.y;
            break;
        case Dir.Right:
            next.x = pos.x + 2;
            next.y = pos.y;
            break;
    }
    return next;
}

// 根据坐标获得地图数据
int GetMapType(Pos pos)
{
    return map[pos.y, pos.x];
}

// 修改地图数据
void ChangeMapType(Pos pos, int type)
{
    map[pos.y, pos.x] = type;
}


// 初始化
void Init()
{
    // 初始化窗口
    Console.CursorVisible = false;
    Console.Title = "GameLoop";
    Console.SetWindowSize(100, 30);

    // 绘制边框
    Console.Clear();

    // 初始化数据
    map = new int[20, 30];

    // 读取文件
    if (File.Exists(fileName))
    {
        // 读取文件, 这里using表示只在这里读取(使用？)，出了之后释放？以免内存泄漏
        using (StreamReader sr = new StreamReader(fileName))
        {
            string data = sr.ReadToEnd();
            // 字符串分割 Split, 这里的文本中用",""换行"分离
            string[] lines = data.Split('\n'); // 分隔的也可使字符串
            for (int i = 0; i < lines.Length; i++)
            {
                string[] elements = lines[i].Split(',');
                for (int j = 0; j < elements.Length; j++)
                {
                    map[i, j] = int.Parse(elements[j]);
                }
            }
        };
    }

    pos.x = 10;
    pos.y = 10;
    pos.x0 = pos.x;
    pos.y0 = pos.y;
    dir = Dir.None;
}

// 更新
void Update()
{
    Input();
    // 逻辑
    // 如果撞到墙 就不动
    // if (没有撞到墙) Move();
    // 如何获取角色下一格的坐标？
    // 判断下一格坐标是否是墙->如何根据坐标获取地图数据
    if (GetMapType(GetNextPos()) != (int)MapType.Wall &&
        GetMapType(GetNextPos()) != (int)MapType.Box)
    {
        Move();
    }
    // 如何推动箱子
    // 角色面前有箱子，箱子面前没有障碍物
    // 结果 箱子前进一格，角色前进一格
    if (pos.x == pos.x0 && pos.y == pos.y0 &&
        GetMapType(GetNextPos()) == (int)MapType.Box &&
        GetMapType(GetNextNextPos()) != (int)MapType.Box &&
        GetMapType(GetNextNextPos()) != (int)MapType.Wall)
    {
        // 箱子前进一格
        // 箱子的位置（玩家的前一格）变成空地 箱子的前一格（玩家的前前一格）变成箱子
        ChangeMapType(GetNextPos(), (int)MapType.None);
        ChangeMapType(GetNextNextPos(), (int)MapType.Box);
        Move();
    }

    //// 限定坐标范围
    //if (map[i, j] == 0)
    //{
    //    x = x0;
    //    y = y0;
    //}
}

// 渲染
void Render()
{
    // 画面
    DrawMap(map);

    Console.SetCursorPosition(pos.x0, pos.y0);  //如果同时按下左右，或产生在三格左右各有图案，中间无图案，而坐标在中间情况，无法理解原因，
                                                //可能是同时读取左右行动，最终导致坐标回到原点，而原点两次行动的原本位置，故被擦除
                                                //上面描述未必正确，和帧率还是有关，但帧率要极高（远超过144fps）才不会出现错误
    Console.Write(" ");
    pos.y0 = pos.y;
    pos.x0 = pos.x;
    Console.SetCursorPosition(pos.x, pos.y); // 坐标变换 将物体坐标转换成屏幕坐标（比如绘画物体为全角，x需要*2）
    Console.Write("1");
    Console.SetCursorPosition(30, 25);
    Console.Write($"x:{pos.x},y:{pos.y}     x0:{pos.x0},y0:{pos.y0}");
}

// 结束
void End()
{

}

// 游戏本体
void Game()
{
    Init(); // 仅在游戏开始时执行一次
    lastTime = DateTime.Now.Ticks;
    while (!isOver)
    {
        // Input(); 按键读取可以放在这里Game()这里
        Update(); // 游戏运行时 不断的更新 逻辑相关都写在这里

        currentTime = DateTime.Now.Ticks;
        if (currentTime - lastTime > 10_000_000 / fps)
        {
            Render(); // 在更新之后 不需要每次循环都更新
            lastTime = DateTime.Now.Ticks;
        }
    }
    End(); // 释放内存
}

// 运行游戏
Game();


// 顶级语句规定自定义类型必须放在最后
// 结构体 struct 自定义的数据集合
struct Pos
{
    public int x;
    public int y;
    public int x0;
    public int y0;
};

// 枚举 enum 给数字取名字
// 更直观的了解数字的含义
enum Dir
{
    None,
    Up, 
    Down, 
    Left, 
    Right,
};

enum MapType
{
    None = -1,
    Wall,
    Box,
    Exit
};